library(tidyverse)
library(forcats)
library(broom)
library(wordcloud)
library(tidytext)
library(viridis)

set.seed(1234)
theme_set(theme_minimal())

Methods for obtaining data online

There are many ways to obtain data from the Internet. Four major categories are:

  • click-and-download on the internet as a “flat” file, such as .csv, .xls
  • install-and-play an API for which someone has written a handy R package
  • API-query published with an unwrapped API
  • Scraping implicit in an html website

Click-and-Download

In the simplest case, the data you need is already on the internet in a tabular format. There are a couple of strategies here:

  • Use read.csv or readr::read_csv to read the data straight into R
  • Use the downloader package or curl from the shell to download the file and store a local copy, then use read_csv or something similar to read the data into R
    • Even if the file disappears from the internet, you have a local copy cached

Even in this instance, files may need cleaning and transformation when you bring them into R.

Data supplied on the web

Many times, the data that you want is not already organized into one or a few tables that you can read directly into R. More frequently, you find this data is given in the form of an API. Application Programming Interfaces (APIs) are descriptions of the kind of requests that can be made of a certain piece of software, and descriptions of the kind of answers that are returned. Many sources of data - databases, websites, services - have made all (or part) of their data available via APIs over the internet. Computer programs (“clients”) can make requests of the server, and the server will respond by sending data (or an error message). This client can be many kinds of other programs or websites, including R running from your laptop.

Install and play packages

Many common web services and APIs have been “wrapped”, i.e. R functions have been written around them which send your query to the server and format the response.

Why do we want this?

  • provenance
  • reproducible
  • updating
  • ease
  • scaling

Sightings of birds: rebird

rebird is an R interface for the ebird database. e-Bird lets birders upload sightings of birds, and allows everyone access to those data.

install.packages("rebird")
library(rebird)

Search birds by geography

The ebird website categorizes some popular locations as “Hotspots”. These are areas where there are both lots of birds and lots of birders. Once such location is at Lincoln Park Zoo in Chicago. You can see data for this site at http://ebird.org/ebird/hotspot/L1573785

At that link, you can see a page like this:

Lincoln Park Zoo

Lincoln Park Zoo

The data already look to be organized in a data frame! rebird allows us to read these data directly into R.

The ID code for Lincoln Park Zoo is L1573785

ebirdhotspot(locID = "L1573785") %>%
  as_tibble()
## # A tibble: 24 x 11
##          lng                   locName howMany               sciName
##        <dbl>                     <chr>   <int>                 <chr>
##  1 -87.63272 Lincoln Park Zoo, Chicago       2 Nycticorax nycticorax
##  2 -87.63272 Lincoln Park Zoo, Chicago      15    Larus delawarensis
##  3 -87.63272 Lincoln Park Zoo, Chicago      15       Hirundo rustica
##  4 -87.63272 Lincoln Park Zoo, Chicago       2  Poecile atricapillus
##  5 -87.63272 Lincoln Park Zoo, Chicago       1    Sitta carolinensis
##  6 -87.63272 Lincoln Park Zoo, Chicago      15      Sturnus vulgaris
##  7 -87.63272 Lincoln Park Zoo, Chicago      13   Agelaius phoeniceus
##  8 -87.63272 Lincoln Park Zoo, Chicago       8    Quiscalus quiscula
##  9 -87.63272 Lincoln Park Zoo, Chicago       3        Spinus tristis
## 10 -87.63272 Lincoln Park Zoo, Chicago      35     Passer domesticus
## # ... with 14 more rows, and 7 more variables: obsValid <lgl>,
## #   locationPrivate <lgl>, obsDt <chr>, obsReviewed <lgl>, comName <chr>,
## #   lat <dbl>, locID <chr>

We can use the function ebirdgeo to get a list for an area. (Note that South and West are negative):

chibirds <- ebirdgeo(lat = 41.8781, lng = -87.6298)
chibirds %>%
  as_tibble() %>%
  str()
## Classes 'tbl_df', 'tbl' and 'data.frame':    137 obs. of  11 variables:
##  $ lng            : num  -87.6 -87.6 -87.6 -87.6 -87.6 ...
##  $ locName        : chr  "U. of Chicago-University Ave. from 61st to 58th" "U. of Chicago-University Ave. from 61st to 58th" "U. of Chicago-University Ave. from 61st to 58th" "U. of Chicago-University Ave. from 61st to 58th" ...
##  $ howMany        : int  5 2 2 3 1 3 4 3 7 1 ...
##  $ sciName        : chr  "Corvus brachyrhynchos" "Passer domesticus" "Chaetura pelagica" "Spizella passerina" ...
##  $ obsValid       : logi  TRUE TRUE TRUE TRUE TRUE TRUE ...
##  $ locationPrivate: logi  TRUE TRUE TRUE TRUE TRUE TRUE ...
##  $ obsDt          : chr  "2017-07-12 08:40" "2017-07-12 08:40" "2017-07-12 08:40" "2017-07-12 08:40" ...
##  $ obsReviewed    : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
##  $ comName        : chr  "American Crow" "House Sparrow" "Chimney Swift" "Chipping Sparrow" ...
##  $ lat            : num  41.8 41.8 41.8 41.8 41.8 ...
##  $ locID          : chr  "L1242933" "L1242933" "L1242933" "L1242933" ...

Note: Check the defaults on this function. e.g. radius of circle, time of year.

We can also search by “region”, which refers to short codes which serve as common shorthands for different political units. For example, France is represented by the letters FR

frenchbirds <- ebirdregion("FR")

frenchbirds %>%
  as_tibble() %>%
  str()
## Classes 'tbl_df', 'tbl' and 'data.frame':    267 obs. of  11 variables:
##  $ lng            : num  7.75 -1.17 -1.17 -1.17 -1.17 ...
##  $ locName        : chr  "Strasbourg (cité)" "Sainte-Eulalie-en-Born " "Sainte-Eulalie-en-Born " "Sainte-Eulalie-en-Born " ...
##  $ howMany        : int  1 8 1 3 5 2 3 25 1 2 ...
##  $ sciName        : chr  "Fulica atra" "Hirundo rustica" "Troglodytes troglodytes" "Motacilla alba" ...
##  $ obsValid       : logi  TRUE TRUE TRUE TRUE TRUE TRUE ...
##  $ locationPrivate: logi  FALSE TRUE TRUE TRUE TRUE TRUE ...
##  $ obsDt          : chr  "2017-07-12 12:27" "2017-07-12 07:00" "2017-07-12 07:00" "2017-07-12 07:00" ...
##  $ obsReviewed    : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
##  $ comName        : chr  "Eurasian Coot" "Barn Swallow" "Eurasian Wren" "White Wagtail" ...
##  $ lat            : num  48.6 44.3 44.3 44.3 44.3 ...
##  $ locID          : chr  "L3737750" "L6072426" "L6072426" "L6072426" ...

Find out WHEN a bird has been seen in a certain place! Choosing a name from chibirds above (the Bald Eagle):

warbler <- ebirdgeo(species = 'Setophaga coronata', lat = 41.8781, lng = -87.6298)

warbler %>%
  as_tibble() %>%
  str()
## Classes 'tbl_df', 'tbl' and 'data.frame':    0 obs. of  0 variables

rebird knows where you are:

ebirdgeo(species = 'Setophaga coronata') %>%
  as_tibble() %>%
  knitr::kable()
## Warning: As a complete lat/long pair was not provided, your location was
## determined using your computer's public-facing IP address. This will likely
## not reflect your physical location if you are using a remote server or
## proxy.

Searching geographic info: geonames

# install.packages(geonames)
library(geonames)

API authentication

Many APIs require you to register for access. This allows them to track which users are submitting queries and manage demand - if you submit too many queries too quickly, you might be rate-limited and your requests de-prioritized or blocked. Always check the API access policy of the web site to determine what these limits are.

There are a few things we need to do to be able to use this package to access the geonames API:

  1. go to the geonames site and register an account.
  2. click here to enable the free web service
  3. Tell R your geonames username. You could run the line
options(geonamesUsername = "my_user_name")

in R. However this is insecure. We don’t want to risk committing this line and pushing it to our public GitHub page! Instead, you should create a file in the same place as your .Rproj file. Name this file .Rprofile, and add

options(geonamesUsername = "my_user_name")

to that file.

Important

  • Make sure your .Rprofile ends with a blank line
  • Make sure .Rprofile is included in your .gitignore file, otherwise it will be synced with Github
  • Restart RStudio after modifying .Rprofile in order to load any new keys into memory
  • Spelling is important when you set the option in your .Rprofile
  • You can do a similar process for an arbitrary package or key. For example:
# in .Rprofile
options("this_is_my_key" = XXXX)

# later, in the R script:
key <- getOption("this_is_my_key")

This is a simple means to keep your keys private, especially if you are sharing the same authentication across several projects. Remember that using .Rprofile makes your code un-reproducible. In this case, that is exactly what we want!

Using Geonames

What can we do? Get access to lots of geographical information via the various “web services”

countryInfo <- GNcountryInfo()
countryInfo %>%
  as_tibble() %>%
  str()
## Classes 'tbl_df', 'tbl' and 'data.frame':    250 obs. of  17 variables:
##  $ continent    : chr  "EU" "AS" "AS" "NA" ...
##  $ capital      : chr  "Andorra la Vella" "Abu Dhabi" "Kabul" "Saint John’s" ...
##  $ languages    : chr  "ca" "ar-AE,fa,en,hi,ur" "fa-AF,ps,uz-AF,tk" "en-AG" ...
##  $ geonameId    : chr  "3041565" "290557" "1149361" "3576396" ...
##  $ south        : chr  "42.4284925987684" "22.6333293914795" "29.377472" "16.996979" ...
##  $ isoAlpha3    : chr  "AND" "ARE" "AFG" "ATG" ...
##  $ north        : chr  "42.6560438963" "26.0841598510742" "38.483418" "17.729387" ...
##  $ fipsCode     : chr  "AN" "AE" "AF" "AC" ...
##  $ population   : chr  "84000" "4975593" "29121286" "86754" ...
##  $ east         : chr  "1.78654277783198" "56.3816604614258" "74.879448" "-61.672421" ...
##  $ isoNumeric   : chr  "020" "784" "004" "028" ...
##  $ areaInSqKm   : chr  "468.0" "82880.0" "647500.0" "443.0" ...
##  $ countryCode  : chr  "AD" "AE" "AF" "AG" ...
##  $ west         : chr  "1.40718671411128" "51.5833282470703" "60.478443" "-61.906425" ...
##  $ countryName  : chr  "Principality of Andorra" "United Arab Emirates" "Islamic Republic of Afghanistan" "Antigua and Barbuda" ...
##  $ continentName: chr  "Europe" "Asia" "Asia" "North America" ...
##  $ currencyCode : chr  "EUR" "AED" "AFN" "XCD" ...

This country info dataset is very helpful for accessing the rest of the data, because it gives us the standardized codes for country and language.

The Manifesto Project: manifestoR

The Manifesto Project collects and organizes political party manifestos from around the world. It currently covers over 1000 parties from 1945 until today in over 50 countries on five continents. We can use the manifestoR package to access the API and download those manifestos for analysis in R.

Load library and set API key

Accessing data from the Manifesto Project API requires an authentication key. You can create an account and key here. Here I store my key in .Rprofile and retrieve it using mp_setapikey().

library(manifestoR)

# retrieve API key stored in .Rprofile
mp_setapikey(key = getOption("manifesto_key"))

Retrieve the database

(mpds <- mp_maindataset())
## Connecting to Manifesto Project DB API... 
## Connecting to Manifesto Project DB API... corpus version: 2016-6
## # A tibble: 4,121 x 173
##    country countryname oecdmember eumember      edate   date party
##      <dbl>       <chr>      <dbl>    <dbl>     <date>  <dbl> <dbl>
##  1      11      Sweden          0        0 1944-09-17 194409 11220
##  2      11      Sweden          0        0 1944-09-17 194409 11320
##  3      11      Sweden          0        0 1944-09-17 194409 11420
##  4      11      Sweden          0        0 1944-09-17 194409 11620
##  5      11      Sweden          0        0 1944-09-17 194409 11810
##  6      11      Sweden          0        0 1948-09-19 194809 11220
##  7      11      Sweden          0        0 1948-09-19 194809 11320
##  8      11      Sweden          0        0 1948-09-19 194809 11420
##  9      11      Sweden          0        0 1948-09-19 194809 11620
## 10      11      Sweden          0        0 1948-09-19 194809 11810
## # ... with 4,111 more rows, and 166 more variables: partyname <chr>,
## #   partyabbrev <chr>, parfam <dbl>, coderid <dbl>, manual <dbl>,
## #   coderyear <dbl>, testresult <dbl>, testeditsim <dbl>, pervote <dbl>,
## #   voteest <dbl>, presvote <dbl>, absseat <dbl>, totseats <dbl>,
## #   progtype <dbl>, datasetorigin <dbl>, corpusversion <chr>, total <dbl>,
## #   peruncod <dbl>, per101 <dbl>, per102 <dbl>, per103 <dbl>,
## #   per104 <dbl>, per105 <dbl>, per106 <dbl>, per107 <dbl>, per108 <dbl>,
## #   per109 <dbl>, per110 <dbl>, per201 <dbl>, per202 <dbl>, per203 <dbl>,
## #   per204 <dbl>, per301 <dbl>, per302 <dbl>, per303 <dbl>, per304 <dbl>,
## #   per305 <dbl>, per401 <dbl>, per402 <dbl>, per403 <dbl>, per404 <dbl>,
## #   per405 <dbl>, per406 <dbl>, per407 <dbl>, per408 <dbl>, per409 <dbl>,
## #   per410 <dbl>, per411 <dbl>, per412 <dbl>, per413 <dbl>, per414 <dbl>,
## #   per415 <dbl>, per416 <dbl>, per501 <dbl>, per502 <dbl>, per503 <dbl>,
## #   per504 <dbl>, per505 <dbl>, per506 <dbl>, per507 <dbl>, per601 <dbl>,
## #   per602 <dbl>, per603 <dbl>, per604 <dbl>, per605 <dbl>, per606 <dbl>,
## #   per607 <dbl>, per608 <dbl>, per701 <dbl>, per702 <dbl>, per703 <dbl>,
## #   per704 <dbl>, per705 <dbl>, per706 <dbl>, per1011 <dbl>,
## #   per1012 <dbl>, per1013 <dbl>, per1014 <dbl>, per1015 <dbl>,
## #   per1016 <dbl>, per1021 <dbl>, per1022 <dbl>, per1023 <dbl>,
## #   per1024 <dbl>, per1025 <dbl>, per1026 <dbl>, per1031 <dbl>,
## #   per1032 <dbl>, per1033 <dbl>, per2021 <dbl>, per2022 <dbl>,
## #   per2023 <dbl>, per2031 <dbl>, per2032 <dbl>, per2033 <dbl>,
## #   per2041 <dbl>, per3011 <dbl>, per3051 <dbl>, per3052 <dbl>,
## #   per3053 <dbl>, ...

mp_maindataset() includes a data frame describing each manifesto included in the database. You can use this database for some exploratory data analysis. For instance, how many manifestos have been published by each political party in Sweden?

mpds %>%
  filter(countryname == "Sweden") %>%
  count(partyname) %>%
  ggplot(aes(fct_reorder(partyname, n), n)) +
  geom_col() +
  labs(title = "Political manifestos published in Sweden",
       x = NULL,
       y = "Total (1948-present)") +
  coord_flip()

Or we can use scaling functions to identify each party manifesto on an ideological dimension. For example, how have the Democratic and Republican Party manifestos in the United States changed over time?

mpds %>%
  filter(party == 61320 | party == 61620) %>%
  mutate(ideo = mp_scale(.)) %>%
  select(partyname, edate, ideo) %>%
  ggplot(aes(edate, ideo, color = partyname)) +
  geom_line() +
  scale_color_manual(values = c("blue", "red")) +
  labs(title = "Ideological scaling of major US political parties",
       x = "Year",
       y = "Ideological position",
       color = NULL) +
  theme(legend.position = "bottom")

Download manifestos

mp_corpus() can be used to download the original manifestos as full text documents stored as a corpus. Once you obtain the corpus, you can perform text analysis. As an example, let’s compare the most common words in the Democratic and Republican Party manifestos from the 2012 U.S. presidential election:

# download documents
(docs <- mp_corpus(countryname == "United States" & edate > as.Date("2012-01-01")))
## Connecting to Manifesto Project DB API... 
## Connecting to Manifesto Project DB API... corpus version: 2017-1 
## Connecting to Manifesto Project DB API... 
## Connecting to Manifesto Project DB API... corpus version: 2017-1 
## Connecting to Manifesto Project DB API... corpus version: 2017-1 
## Connecting to Manifesto Project DB API... corpus version: 2017-1
## <<ManifestoCorpus>>
## Metadata:  corpus specific: 0, document level (indexed): 0
## Content:  documents: 2
# generate wordcloud of most common terms
docs %>%
  tidy() %>%
  mutate(party = factor(party, levels = c(61320, 61620),
                        labels = c("Democratic Party", "Republican Party"))) %>%
  unnest_tokens(word, text) %>%
  anti_join(stop_words) %>%
  count(party, word, sort = TRUE) %>%
  na.omit() %>%
  reshape2::acast(word ~ party, value.var = "n", fill = 0) %>%
  comparison.cloud(max.words = 200)

Census data with tidycensus

tidycensus provides an interface with the US Census Bureau’s decennial census and American Community APIs and returns tidy data frames with optional simple feature geometry. These APIs require a free key you can obtain here. Rather than storing your key in .Rprofile, tidycensus includes census_api_key() which automatically stores your key in .Renviron, which is basically a global version of .Rprofile. Anything stored in .Renviron is automatically loaded anytime you initiate R on your computer, regardless of the project or file location. Once you get your key, load it:

library(tidycensus)
census_api_key("YOUR API KEY GOES HERE")

Obtaining data

get_decennial() allows you to obtain data from the 1990, 2000, and 2010 decennial US censuses. Let’s look at the number of individuals of Chinese ethnicity by state in 2010:

china10 <- get_decennial(geography = "state", variables = "PCT0060007", year = 2010)
## Getting data from the 2010 decennial Census
china10
## # A tibble: 52 x 4
##    GEOID NAME                 variable     value
##    <chr> <chr>                <chr>        <dbl>
##  1 01    Alabama              PCT0060007    9287
##  2 02    Alaska               PCT0060007    2268
##  3 04    Arizona              PCT0060007   32827
##  4 05    Arkansas             PCT0060007    5101
##  5 06    California           PCT0060007 1241572
##  6 08    Colorado             PCT0060007   25589
##  7 09    Connecticut          PCT0060007   31514
##  8 10    Delaware             PCT0060007    6103
##  9 11    District of Columbia PCT0060007    5166
## 10 12    Florida              PCT0060007   72375
## # ... with 42 more rows

The result of get_decennial() is a tidy data frame with one row per geographic unit-variable.

  • GEOID - identifier for the geographical unit associated with the row
  • NAME - descriptive name of the geographical unit
  • variable - the Census variable encoded in the row
  • value - the value of the variable for that geographic unit

We can quickly visualize this data frame using ggplot2:

ggplot(china10, aes(x = reorder(NAME, value), y = value)) +
  geom_point() +
  coord_flip()

Of course this graph is not entirely useful since it is based on the raw frequency of Chinese individuals. California is at the top of the list, but it is also the most populous city. Instead, we could normalize this value as a percentage of the entire state population. To do that, we need to retrieve another variable:

china_pop <- get_decennial(geography = "state",
                           variables = c("PCT0060007", "P0010001"),
                           year = 2010) %>%
  spread(variable, value) %>%
  mutate(pct_chinese = PCT0060007 / P0010001)
## Getting data from the 2010 decennial Census
china_pop
## # A tibble: 52 x 5
##    GEOID NAME                 P0010001 PCT0060007 pct_chinese
##    <chr> <chr>                   <dbl>      <dbl>       <dbl>
##  1 01    Alabama               4779736       9287     0.00194
##  2 02    Alaska                 710231       2268     0.00319
##  3 04    Arizona               6392017      32827     0.00514
##  4 05    Arkansas              2915918       5101     0.00175
##  5 06    California           37253956    1241572     0.0333 
##  6 08    Colorado              5029196      25589     0.00509
##  7 09    Connecticut           3574097      31514     0.00882
##  8 10    Delaware               897934       6103     0.00680
##  9 11    District of Columbia   601723       5166     0.00859
## 10 12    Florida              18801310      72375     0.00385
## # ... with 42 more rows
ggplot(china_pop, aes(x = reorder(NAME, pct_chinese), y = pct_chinese)) +
  geom_point() +
  scale_y_continuous(labels = scales::percent) +
  coord_flip()

get_acs() retrieves data from the American Community Survey. This survey is administered to a sample of 3 million households on an annual basis, so the data points are estimates characterized by a margin of error. tidycensus returns both the original estimate and margin of error. Let’s get median household income data from the 2012-2016 ACS for counties in Illinois.

usa_inc <- get_acs(geography = "state", 
                   variables = c(medincome = "B19013_001"), 
                   year = 2016)
## Getting data from the 2012-2016 5-year ACS
usa_inc
## # A tibble: 52 x 5
##    GEOID NAME                 variable  estimate   moe
##    <chr> <chr>                <chr>        <dbl> <dbl>
##  1 01    Alabama              medincome    44758   314
##  2 02    Alaska               medincome    74444   809
##  3 04    Arizona              medincome    51340   231
##  4 05    Arkansas             medincome    42336   234
##  5 06    California           medincome    63783   188
##  6 08    Colorado             medincome    62520   287
##  7 09    Connecticut          medincome    71755   473
##  8 10    Delaware             medincome    61017   723
##  9 11    District of Columbia medincome    72935  1164
## 10 12    Florida              medincome    48900   200
## # ... with 42 more rows

Now we return both an estimate column for the ACS estimate and moe for the margin of error (defaults to 90% confidence interval).

usa_inc %>%
  ggplot(aes(x = reorder(NAME, estimate), y = estimate)) +
  geom_pointrange(aes(ymin = estimate - moe,
                     ymax = estimate + moe),
                  size = .25) +
  coord_flip() +
  labs(title = "Household income by state",
       subtitle = "2012-2016 American Community Survey",
       x = "",
       y = "ACS estimate (bars represent margin of error)")

Search for variables

get_() requires knowing the variable ID, of which there are thousands. load_variables() downloads a list of variable IDs and labels for a given Census or ACS and dataset. You can then use View() to interactively browse through and filter for variables in RStudio.

Drawing maps

tidycensus also can return simple feature geometry for geographic units along with variables from the decennial Census or ACS, which can then be visualized using geom_sf(). Let’s look at median household incomeby Census tracts from the 2012-2016 ACS in Loudoun County, Virginia:

loudoun <- get_acs(state = "VA",
                   county = "Loudoun",
                   geography = "tract", 
                   variables = c(medincome = "B19013_001"), 
                   year = 2016,
                   geometry = TRUE)
## 
  |                                                                       
  |                                                                 |   0%
  |                                                                       
  |=                                                                |   1%
  |                                                                       
  |=                                                                |   2%
  |                                                                       
  |==                                                               |   3%
  |                                                                       
  |===                                                              |   5%
  |                                                                       
  |====                                                             |   6%
  |                                                                       
  |=====                                                            |   7%
  |                                                                       
  |=====                                                            |   8%
  |                                                                       
  |======                                                           |   9%
  |                                                                       
  |======                                                           |  10%
  |                                                                       
  |=======                                                          |  11%
  |                                                                       
  |========                                                         |  12%
  |                                                                       
  |========                                                         |  13%
  |                                                                       
  |=========                                                        |  14%
  |                                                                       
  |==========                                                       |  15%
  |                                                                       
  |===========                                                      |  16%
  |                                                                       
  |===========                                                      |  17%
  |                                                                       
  |============                                                     |  18%
  |                                                                       
  |============                                                     |  19%
  |                                                                       
  |=============                                                    |  20%
  |                                                                       
  |==============                                                   |  21%
  |                                                                       
  |==============                                                   |  22%
  |                                                                       
  |===============                                                  |  23%
  |                                                                       
  |================                                                 |  25%
  |                                                                       
  |=================                                                |  26%
  |                                                                       
  |==================                                               |  27%
  |                                                                       
  |==================                                               |  28%
  |                                                                       
  |===================                                              |  29%
  |                                                                       
  |===================                                              |  30%
  |                                                                       
  |====================                                             |  31%
  |                                                                       
  |=====================                                            |  32%
  |                                                                       
  |=====================                                            |  33%
  |                                                                       
  |======================                                           |  34%
  |                                                                       
  |=======================                                          |  35%
  |                                                                       
  |========================                                         |  36%
  |                                                                       
  |========================                                         |  37%
  |                                                                       
  |=========================                                        |  38%
  |                                                                       
  |=========================                                        |  39%
  |                                                                       
  |==========================                                       |  40%
  |                                                                       
  |===========================                                      |  41%
  |                                                                       
  |===========================                                      |  42%
  |                                                                       
  |============================                                     |  43%
  |                                                                       
  |=============================                                    |  44%
  |                                                                       
  |=============================                                    |  45%
  |                                                                       
  |==============================                                   |  46%
  |                                                                       
  |===============================                                  |  47%
  |                                                                       
  |===============================                                  |  48%
  |                                                                       
  |================================                                 |  49%
  |                                                                       
  |================================                                 |  50%
  |                                                                       
  |=================================                                |  51%
  |                                                                       
  |==================================                               |  52%
  |                                                                       
  |==================================                               |  53%
  |                                                                       
  |===================================                              |  54%
  |                                                                       
  |====================================                             |  55%
  |                                                                       
  |=====================================                            |  56%
  |                                                                       
  |=====================================                            |  57%
  |                                                                       
  |======================================                           |  58%
  |                                                                       
  |======================================                           |  59%
  |                                                                       
  |=======================================                          |  60%
  |                                                                       
  |========================================                         |  61%
  |                                                                       
  |========================================                         |  62%
  |                                                                       
  |=========================================                        |  63%
  |                                                                       
  |==========================================                       |  64%
  |                                                                       
  |==========================================                       |  65%
  |                                                                       
  |===========================================                      |  66%
  |                                                                       
  |============================================                     |  67%
  |                                                                       
  |============================================                     |  68%
  |                                                                       
  |=============================================                    |  69%
  |                                                                       
  |=============================================                    |  70%
  |                                                                       
  |==============================================                   |  71%
  |                                                                       
  |===============================================                  |  72%
  |                                                                       
  |===============================================                  |  73%
  |                                                                       
  |================================================                 |  74%
  |                                                                       
  |=================================================                |  75%
  |                                                                       
  |=================================================                |  76%
  |                                                                       
  |==================================================               |  77%
  |                                                                       
  |===================================================              |  78%
  |                                                                       
  |===================================================              |  79%
  |                                                                       
  |====================================================             |  80%
  |                                                                       
  |=====================================================            |  81%
  |                                                                       
  |=====================================================            |  82%
  |                                                                       
  |======================================================           |  83%
  |                                                                       
  |=======================================================          |  84%
  |                                                                       
  |=======================================================          |  85%
  |                                                                       
  |========================================================         |  86%
  |                                                                       
  |=========================================================        |  87%
  |                                                                       
  |=========================================================        |  88%
  |                                                                       
  |==========================================================       |  89%
  |                                                                       
  |==========================================================       |  90%
  |                                                                       
  |===========================================================      |  91%
  |                                                                       
  |============================================================     |  92%
  |                                                                       
  |=============================================================    |  94%
  |                                                                       
  |==============================================================   |  95%
  |                                                                       
  |==============================================================   |  96%
  |                                                                       
  |===============================================================  |  97%
  |                                                                       
  |================================================================ |  98%
  |                                                                       
  |================================================================ |  99%
  |                                                                       
  |=================================================================| 100%
loudoun
## Simple feature collection with 65 features and 5 fields
## geometry type:  MULTIPOLYGON
## dimension:      XY
## bbox:           xmin: -77.96196 ymin: 38.84645 xmax: -77.32828 ymax: 39.32419
## epsg (SRID):    4269
## proj4string:    +proj=longlat +datum=NAD83 +no_defs
## First 10 features:
##          GEOID                                           NAME  variable
## 1  51107610503 Census Tract 6105.03, Loudoun County, Virginia medincome
## 2  51107610504 Census Tract 6105.04, Loudoun County, Virginia medincome
## 3  51107611006 Census Tract 6110.06, Loudoun County, Virginia medincome
## 4  51107611013 Census Tract 6110.13, Loudoun County, Virginia medincome
## 5  51107611205 Census Tract 6112.05, Loudoun County, Virginia medincome
## 6  51107610505 Census Tract 6105.05, Loudoun County, Virginia medincome
## 7  51107610603 Census Tract 6106.03, Loudoun County, Virginia medincome
## 8  51107611011 Census Tract 6110.11, Loudoun County, Virginia medincome
## 9  51107611014 Census Tract 6110.14, Loudoun County, Virginia medincome
## 10 51107611501 Census Tract 6115.01, Loudoun County, Virginia medincome
##    estimate   moe                       geometry
## 1    150982  6323 MULTIPOLYGON (((-77.54714 3...
## 2    108042  4652 MULTIPOLYGON (((-77.56114 3...
## 3    140365 16252 MULTIPOLYGON (((-77.48743 3...
## 4    144638 22972 MULTIPOLYGON (((-77.50032 3...
## 5    101910  9251 MULTIPOLYGON (((-77.39145 3...
## 6     45226  7533 MULTIPOLYGON (((-77.56454 3...
## 7     50818  6995 MULTIPOLYGON (((-77.5735 39...
## 8     96875 14504 MULTIPOLYGON (((-77.51117 3...
## 9    118077 17241 MULTIPOLYGON (((-77.48567 3...
## 10    71356 17165 MULTIPOLYGON (((-77.43106 3...

This looks similar to the previous output but because we set geometry = TRUE it is now a simple features data frame with a geometry column defining the geographic feature. We can visualize it using geom_sf() and viridis::scale_*_viridis() to adjust the color palette.

ggplot(data = loudoun) +
  geom_sf(aes(fill = estimate, color = estimate)) + 
  coord_sf(crs = 26911) + 
  scale_fill_viridis(option = "magma") + 
  scale_color_viridis(option = "magma")

Acknowledgments

Session Info

devtools::session_info()
## Session info -------------------------------------------------------------
##  setting  value                       
##  version  R version 3.5.1 (2018-07-02)
##  system   x86_64, darwin15.6.0        
##  ui       X11                         
##  language (EN)                        
##  collate  en_US.UTF-8                 
##  tz       America/Chicago             
##  date     2019-01-02
## Packages -----------------------------------------------------------------
##  package      * version date       source        
##  assertthat     0.2.0   2017-04-11 CRAN (R 3.5.0)
##  backports      1.1.2   2017-12-13 CRAN (R 3.5.0)
##  base         * 3.5.1   2018-07-05 local         
##  base64enc      0.1-3   2015-07-28 CRAN (R 3.5.0)
##  bindr          0.1.1   2018-03-13 CRAN (R 3.5.0)
##  bindrcpp       0.2.2   2018-03-29 CRAN (R 3.5.0)
##  broom        * 0.5.0   2018-07-17 CRAN (R 3.5.0)
##  cellranger     1.1.0   2016-07-27 CRAN (R 3.5.0)
##  cli            1.0.0   2017-11-05 CRAN (R 3.5.0)
##  colorspace     1.3-2   2016-12-14 CRAN (R 3.5.0)
##  compiler       3.5.1   2018-07-05 local         
##  crayon         1.3.4   2017-09-16 CRAN (R 3.5.0)
##  datasets     * 3.5.1   2018-07-05 local         
##  devtools       1.13.6  2018-06-27 CRAN (R 3.5.0)
##  digest         0.6.18  2018-10-10 cran (@0.6.18)
##  dplyr        * 0.7.8   2018-11-10 cran (@0.7.8) 
##  DT             0.4     2018-01-30 CRAN (R 3.5.0)
##  evaluate       0.11    2018-07-17 CRAN (R 3.5.0)
##  forcats      * 0.3.0   2018-02-19 CRAN (R 3.5.0)
##  foreign        0.8-71  2018-07-20 CRAN (R 3.5.0)
##  functional     0.6     2014-07-16 CRAN (R 3.5.0)
##  ggplot2      * 3.1.0   2018-10-25 cran (@3.1.0) 
##  glue           1.3.0   2018-07-17 CRAN (R 3.5.0)
##  graphics     * 3.5.1   2018-07-05 local         
##  grDevices    * 3.5.1   2018-07-05 local         
##  grid           3.5.1   2018-07-05 local         
##  gridExtra      2.3     2017-09-09 CRAN (R 3.5.0)
##  gtable         0.2.0   2016-02-26 CRAN (R 3.5.0)
##  haven          1.1.2   2018-06-27 CRAN (R 3.5.0)
##  hms            0.4.2   2018-03-10 CRAN (R 3.5.0)
##  htmltools      0.3.6   2017-04-28 CRAN (R 3.5.0)
##  htmlwidgets    1.2     2018-04-19 CRAN (R 3.5.0)
##  httr           1.3.1   2017-08-20 CRAN (R 3.5.0)
##  janeaustenr    0.1.5   2017-06-10 CRAN (R 3.5.0)
##  jsonlite       1.5     2017-06-01 CRAN (R 3.5.0)
##  knitr          1.20    2018-02-20 CRAN (R 3.5.0)
##  lattice        0.20-35 2017-03-25 CRAN (R 3.5.1)
##  lazyeval       0.2.1   2017-10-29 CRAN (R 3.5.0)
##  lubridate      1.7.4   2018-04-11 CRAN (R 3.5.0)
##  magrittr       1.5     2014-11-22 CRAN (R 3.5.0)
##  manifestoR   * 1.3.0   2018-05-28 CRAN (R 3.5.0)
##  Matrix         1.2-14  2018-04-13 CRAN (R 3.5.1)
##  memoise        1.1.0   2017-04-21 CRAN (R 3.5.0)
##  methods      * 3.5.1   2018-07-05 local         
##  mnormt         1.5-5   2016-10-15 CRAN (R 3.5.0)
##  modelr         0.1.2   2018-05-11 CRAN (R 3.5.0)
##  munsell        0.5.0   2018-06-12 CRAN (R 3.5.0)
##  nlme           3.1-137 2018-04-07 CRAN (R 3.5.1)
##  NLP          * 0.1-11  2017-08-15 CRAN (R 3.5.0)
##  parallel       3.5.1   2018-07-05 local         
##  pillar         1.3.0   2018-07-14 CRAN (R 3.5.0)
##  pkgconfig      2.0.2   2018-08-16 CRAN (R 3.5.1)
##  plyr           1.8.4   2016-06-08 CRAN (R 3.5.0)
##  psych          1.8.4   2018-05-06 CRAN (R 3.5.0)
##  purrr        * 0.2.5   2018-05-29 CRAN (R 3.5.0)
##  R6             2.3.0   2018-10-04 cran (@2.3.0) 
##  RColorBrewer * 1.1-2   2014-12-07 CRAN (R 3.5.0)
##  Rcpp           1.0.0   2018-11-07 cran (@1.0.0) 
##  readr        * 1.1.1   2017-05-16 CRAN (R 3.5.0)
##  readxl         1.1.0   2018-04-20 CRAN (R 3.5.0)
##  rlang          0.3.0.1 2018-10-25 CRAN (R 3.5.0)
##  rmarkdown      1.10    2018-06-11 CRAN (R 3.5.0)
##  rprojroot      1.3-2   2018-01-03 CRAN (R 3.5.0)
##  rstudioapi     0.7     2017-09-07 CRAN (R 3.5.0)
##  rvest          0.3.2   2016-06-17 CRAN (R 3.5.0)
##  scales         1.0.0   2018-08-09 CRAN (R 3.5.0)
##  slam           0.1-43  2018-04-23 CRAN (R 3.5.0)
##  SnowballC      0.5.1   2014-08-09 CRAN (R 3.5.0)
##  stats        * 3.5.1   2018-07-05 local         
##  stringi        1.2.4   2018-07-20 CRAN (R 3.5.0)
##  stringr      * 1.3.1   2018-05-10 CRAN (R 3.5.0)
##  tibble       * 1.4.2   2018-01-22 CRAN (R 3.5.0)
##  tidyr        * 0.8.1   2018-05-18 CRAN (R 3.5.0)
##  tidyselect     0.2.5   2018-10-11 cran (@0.2.5) 
##  tidytext     * 0.2.0   2018-10-17 CRAN (R 3.5.0)
##  tidyverse    * 1.2.1   2017-11-14 CRAN (R 3.5.0)
##  tm           * 0.7-5   2018-07-29 CRAN (R 3.5.1)
##  tokenizers     0.2.1   2018-03-29 CRAN (R 3.5.0)
##  tools          3.5.1   2018-07-05 local         
##  utils        * 3.5.1   2018-07-05 local         
##  viridis      * 0.5.1   2018-03-29 CRAN (R 3.5.0)
##  viridisLite  * 0.3.0   2018-02-01 CRAN (R 3.5.0)
##  withr          2.1.2   2018-03-15 CRAN (R 3.5.0)
##  wordcloud    * 2.5     2014-06-13 CRAN (R 3.5.0)
##  xml2           1.2.0   2018-01-24 CRAN (R 3.5.0)
##  yaml           2.2.0   2018-07-25 CRAN (R 3.5.0)
##  zoo            1.8-3   2018-07-16 CRAN (R 3.5.0)
LS0tCnRpdGxlOiAiR2V0dGluZyBkYXRhIGZyb20gdGhlIHdlYjogQVBJIGFjY2VzcyIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlID0gRkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChjYWNoZSA9IFRSVUUpCmBgYAoKYGBge3IgcGFja2FnZXMsIGNhY2hlID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoZm9yY2F0cykKbGlicmFyeShicm9vbSkKbGlicmFyeSh3b3JkY2xvdWQpCmxpYnJhcnkodGlkeXRleHQpCmxpYnJhcnkodmlyaWRpcykKCnNldC5zZWVkKDEyMzQpCnRoZW1lX3NldCh0aGVtZV9taW5pbWFsKCkpCmBgYAoKIyBNZXRob2RzIGZvciBvYnRhaW5pbmcgZGF0YSBvbmxpbmUKClRoZXJlIGFyZSBtYW55IHdheXMgdG8gb2J0YWluIGRhdGEgZnJvbSB0aGUgSW50ZXJuZXQuIEZvdXIgbWFqb3IgY2F0ZWdvcmllcyBhcmU6CgoqICoqY2xpY2stYW5kLWRvd25sb2FkKiogb24gdGhlIGludGVybmV0IGFzIGEgImZsYXQiIGZpbGUsIHN1Y2ggYXMgLmNzdiwgLnhscwoqICoqaW5zdGFsbC1hbmQtcGxheSoqIGFuIEFQSSBmb3Igd2hpY2ggc29tZW9uZSBoYXMgd3JpdHRlbiBhIGhhbmR5IFIgcGFja2FnZQoqICoqQVBJLXF1ZXJ5KiogcHVibGlzaGVkIHdpdGggYW4gdW53cmFwcGVkIEFQSQoqICoqU2NyYXBpbmcqKiBpbXBsaWNpdCBpbiBhbiBodG1sIHdlYnNpdGUKCiMjIENsaWNrLWFuZC1Eb3dubG9hZAoKSW4gdGhlIHNpbXBsZXN0IGNhc2UsIHRoZSBkYXRhIHlvdSBuZWVkIGlzIGFscmVhZHkgb24gdGhlIGludGVybmV0IGluIGEgdGFidWxhciBmb3JtYXQuIFRoZXJlIGFyZSBhIGNvdXBsZSBvZiBzdHJhdGVnaWVzIGhlcmU6CgoqIFVzZSBgcmVhZC5jc3ZgIG9yIGByZWFkcjo6cmVhZF9jc3ZgIHRvIHJlYWQgdGhlIGRhdGEgc3RyYWlnaHQgaW50byBSCiogVXNlIHRoZSBgZG93bmxvYWRlcmAgcGFja2FnZSBvciBgY3VybGAgZnJvbSB0aGUgc2hlbGwgdG8gZG93bmxvYWQgdGhlIGZpbGUgYW5kIHN0b3JlIGEgbG9jYWwgY29weSwgdGhlbiB1c2UgYHJlYWRfY3N2YCBvciBzb21ldGhpbmcgc2ltaWxhciB0byByZWFkIHRoZSBkYXRhIGludG8gUgogICAgKiBFdmVuIGlmIHRoZSBmaWxlIGRpc2FwcGVhcnMgZnJvbSB0aGUgaW50ZXJuZXQsIHlvdSBoYXZlIGEgbG9jYWwgY29weSBjYWNoZWQKCkV2ZW4gaW4gdGhpcyBpbnN0YW5jZSwgZmlsZXMgbWF5IG5lZWQgY2xlYW5pbmcgYW5kIHRyYW5zZm9ybWF0aW9uIHdoZW4geW91IGJyaW5nIHRoZW0gaW50byBSLgoKIyMgRGF0YSBzdXBwbGllZCBvbiB0aGUgd2ViCgpNYW55IHRpbWVzLCB0aGUgZGF0YSB0aGF0IHlvdSB3YW50IGlzIG5vdCBhbHJlYWR5IG9yZ2FuaXplZCBpbnRvIG9uZSBvciBhIGZldyB0YWJsZXMgdGhhdCB5b3UgY2FuIHJlYWQgZGlyZWN0bHkgaW50byBSLiBNb3JlIGZyZXF1ZW50bHksIHlvdSBmaW5kIHRoaXMgZGF0YSBpcyBnaXZlbiBpbiB0aGUgZm9ybSBvZiBhbiBBUEkuICoqQSoqcHBsaWNhdGlvbiAqKlAqKnJvZ3JhbW1pbmcgKipJKipudGVyZmFjZXMgKEFQSXMpIGFyZSBkZXNjcmlwdGlvbnMgb2YgdGhlIGtpbmQgb2YgcmVxdWVzdHMgdGhhdCBjYW4gYmUgbWFkZSBvZiBhIGNlcnRhaW4gcGllY2Ugb2Ygc29mdHdhcmUsIGFuZCBkZXNjcmlwdGlvbnMgb2YgdGhlIGtpbmQgb2YgYW5zd2VycyB0aGF0IGFyZSByZXR1cm5lZC4gTWFueSBzb3VyY2VzIG9mIGRhdGEgLSBkYXRhYmFzZXMsIHdlYnNpdGVzLCBzZXJ2aWNlcyAtIGhhdmUgbWFkZSBhbGwgKG9yIHBhcnQpIG9mIHRoZWlyIGRhdGEgYXZhaWxhYmxlIHZpYSBBUElzIG92ZXIgdGhlIGludGVybmV0LiBDb21wdXRlciBwcm9ncmFtcyAoImNsaWVudHMiKSBjYW4gbWFrZSByZXF1ZXN0cyBvZiB0aGUgc2VydmVyLCBhbmQgdGhlIHNlcnZlciB3aWxsIHJlc3BvbmQgYnkgc2VuZGluZyBkYXRhIChvciBhbiBlcnJvciBtZXNzYWdlKS4gVGhpcyBjbGllbnQgY2FuIGJlIG1hbnkga2luZHMgb2Ygb3RoZXIgcHJvZ3JhbXMgb3Igd2Vic2l0ZXMsIGluY2x1ZGluZyBSIHJ1bm5pbmcgZnJvbSB5b3VyIGxhcHRvcC4KCiMjIEluc3RhbGwgYW5kIHBsYXkgcGFja2FnZXMKCk1hbnkgY29tbW9uIHdlYiBzZXJ2aWNlcyBhbmQgQVBJcyBoYXZlIGJlZW4gIndyYXBwZWQiLCBpLmUuIFIgZnVuY3Rpb25zIGhhdmUgYmVlbiB3cml0dGVuIGFyb3VuZCB0aGVtIHdoaWNoIHNlbmQgeW91ciBxdWVyeSB0byB0aGUgc2VydmVyIGFuZCBmb3JtYXQgdGhlIHJlc3BvbnNlLgoKV2h5IGRvIHdlIHdhbnQgdGhpcz8KCiogcHJvdmVuYW5jZQoqIHJlcHJvZHVjaWJsZQoqIHVwZGF0aW5nCiogZWFzZQoqIHNjYWxpbmcKCiMgU2lnaHRpbmdzIG9mIGJpcmRzOiBgcmViaXJkYAoKW2ByZWJpcmRgXShodHRwczovL2dpdGh1Yi5jb20vcm9wZW5zY2kvcmViaXJkKSBpcyBhbiBSIGludGVyZmFjZSBmb3IgdGhlIFtlYmlyZF0oaHR0cDovL2ViaXJkLm9yZy9jb250ZW50L2ViaXJkLykgZGF0YWJhc2UuIGUtQmlyZCBsZXRzIGJpcmRlcnMgdXBsb2FkIHNpZ2h0aW5ncyBvZiBiaXJkcywgYW5kIGFsbG93cyBldmVyeW9uZSBhY2Nlc3MgdG8gdGhvc2UgZGF0YS4KCmBgYHtyIHJlYmlyZC1pbnN0YWxsLCBldmFsID0gRkFMU0V9Cmluc3RhbGwucGFja2FnZXMoInJlYmlyZCIpCmBgYAoKYGBge3IgcmViaXJkLCBtZXNzYWdlID0gRkFMU0V9CmxpYnJhcnkocmViaXJkKQpgYGAKCiMjIFNlYXJjaCBiaXJkcyBieSBnZW9ncmFwaHkKClRoZSBlYmlyZCB3ZWJzaXRlIGNhdGVnb3JpemVzIHNvbWUgcG9wdWxhciBsb2NhdGlvbnMgYXMgIkhvdHNwb3RzIi4gVGhlc2UgYXJlIGFyZWFzIHdoZXJlIHRoZXJlIGFyZSBib3RoIGxvdHMgb2YgYmlyZHMgYW5kIGxvdHMgb2YgYmlyZGVycy4gT25jZSBzdWNoIGxvY2F0aW9uIGlzIGF0IExpbmNvbG4gUGFyayBab28gaW4gQ2hpY2Fnby4gWW91IGNhbiBzZWUgZGF0YSBmb3IgdGhpcyBzaXRlIGF0IFtodHRwOi8vZWJpcmQub3JnL2ViaXJkL2hvdHNwb3QvTDE1NzM3ODVdKGh0dHA6Ly9lYmlyZC5vcmcvZWJpcmQvaG90c3BvdC9MMTU3Mzc4NSkKCkF0IHRoYXQgbGluaywgeW91IGNhbiBzZWUgYSBwYWdlIGxpa2UgdGhpczoKCiFbTGluY29sbiBQYXJrIFpvb10oaW1hZ2VzL2xpbmNvbG5fcGFya196b28ucG5nKQoKVGhlIGRhdGEgYWxyZWFkeSBsb29rIHRvIGJlIG9yZ2FuaXplZCBpbiBhIGRhdGEgZnJhbWUhIGByZWJpcmRgIGFsbG93cyB1cyB0byByZWFkIHRoZXNlIGRhdGEgZGlyZWN0bHkgaW50byBSLgoKPiBUaGUgSUQgY29kZSBmb3IgTGluY29sbiBQYXJrIFpvbyBpcyAqKkwxNTczNzg1KioKCmBgYHtyIHJlYmlyZC1saW5jb2xuLXBhcmt9CmViaXJkaG90c3BvdChsb2NJRCA9ICJMMTU3Mzc4NSIpICU+JQogIGFzX3RpYmJsZSgpCmBgYAoKV2UgY2FuIHVzZSB0aGUgZnVuY3Rpb24gYGViaXJkZ2VvYCB0byBnZXQgYSBsaXN0IGZvciBhbiBhcmVhLiAoTm90ZSB0aGF0IFNvdXRoIGFuZCBXZXN0IGFyZSBuZWdhdGl2ZSk6CgpgYGB7ciByZWJpcmQtbG9jYXRpb259CmNoaWJpcmRzIDwtIGViaXJkZ2VvKGxhdCA9IDQxLjg3ODEsIGxuZyA9IC04Ny42Mjk4KQpjaGliaXJkcyAlPiUKICBhc190aWJibGUoKSAlPiUKICBzdHIoKQpgYGAKCioqTm90ZSoqOiBDaGVjayB0aGUgZGVmYXVsdHMgb24gdGhpcyBmdW5jdGlvbi4gZS5nLiByYWRpdXMgb2YgY2lyY2xlLCB0aW1lIG9mIHllYXIuCgpXZSBjYW4gYWxzbyBzZWFyY2ggYnkgInJlZ2lvbiIsIHdoaWNoIHJlZmVycyB0byBzaG9ydCBjb2RlcyB3aGljaCBzZXJ2ZSBhcyBjb21tb24gc2hvcnRoYW5kcyBmb3IgZGlmZmVyZW50IHBvbGl0aWNhbCB1bml0cy4gRm9yIGV4YW1wbGUsIEZyYW5jZSBpcyByZXByZXNlbnRlZCBieSB0aGUgbGV0dGVycyAqKkZSKioKCmBgYHtyIHJlYmlyZC1mcmFuY2V9CmZyZW5jaGJpcmRzIDwtIGViaXJkcmVnaW9uKCJGUiIpCgpmcmVuY2hiaXJkcyAlPiUKICBhc190aWJibGUoKSAlPiUKICBzdHIoKQpgYGAKCkZpbmQgb3V0ICpXSEVOKiBhIGJpcmQgaGFzIGJlZW4gc2VlbiBpbiBhIGNlcnRhaW4gcGxhY2UhIENob29zaW5nIGEgbmFtZSBmcm9tIGBjaGliaXJkc2AgYWJvdmUgKHRoZSBCYWxkIEVhZ2xlKToKCmBgYHtyIHJlYmlyZC13YXJibGVyfQp3YXJibGVyIDwtIGViaXJkZ2VvKHNwZWNpZXMgPSAnU2V0b3BoYWdhIGNvcm9uYXRhJywgbGF0ID0gNDEuODc4MSwgbG5nID0gLTg3LjYyOTgpCgp3YXJibGVyICU+JQogIGFzX3RpYmJsZSgpICU+JQogIHN0cigpCmBgYAoKYHJlYmlyZGAgKiprbm93cyB3aGVyZSB5b3UgYXJlKio6CgpgYGB7ciByZWJpcmQtd2hlcmUtYW0taX0KZWJpcmRnZW8oc3BlY2llcyA9ICdTZXRvcGhhZ2EgY29yb25hdGEnKSAlPiUKICBhc190aWJibGUoKSAlPiUKICBrbml0cjo6a2FibGUoKQpgYGAKCiMgU2VhcmNoaW5nIGdlb2dyYXBoaWMgaW5mbzogYGdlb25hbWVzYAoKYGBge3IgZ2VvbmFtZXMsIG1lc3NhZ2UgPSBGQUxTRX0KIyBpbnN0YWxsLnBhY2thZ2VzKGdlb25hbWVzKQpsaWJyYXJ5KGdlb25hbWVzKQpgYGAKCiMjIEFQSSBhdXRoZW50aWNhdGlvbgoKTWFueSBBUElzIHJlcXVpcmUgeW91IHRvIHJlZ2lzdGVyIGZvciBhY2Nlc3MuIFRoaXMgYWxsb3dzIHRoZW0gdG8gdHJhY2sgd2hpY2ggdXNlcnMgYXJlIHN1Ym1pdHRpbmcgcXVlcmllcyBhbmQgbWFuYWdlIGRlbWFuZCAtIGlmIHlvdSBzdWJtaXQgdG9vIG1hbnkgcXVlcmllcyB0b28gcXVpY2tseSwgeW91IG1pZ2h0IGJlICoqcmF0ZS1saW1pdGVkKiogYW5kIHlvdXIgcmVxdWVzdHMgZGUtcHJpb3JpdGl6ZWQgb3IgYmxvY2tlZC4gQWx3YXlzIGNoZWNrIHRoZSBBUEkgYWNjZXNzIHBvbGljeSBvZiB0aGUgd2ViIHNpdGUgdG8gZGV0ZXJtaW5lIHdoYXQgdGhlc2UgbGltaXRzIGFyZS4KClRoZXJlIGFyZSBhIGZldyB0aGluZ3Mgd2UgbmVlZCB0byBkbyB0byBiZSBhYmxlIHRvIHVzZSB0aGlzIHBhY2thZ2UgdG8gYWNjZXNzIHRoZSBnZW9uYW1lcyBBUEk6CgoxLiBnbyB0byBbdGhlIGdlb25hbWVzIHNpdGVdKGh0dHA6Ly93d3cuZ2VvbmFtZXMub3JnL2xvZ2luLykgYW5kIHJlZ2lzdGVyIGFuIGFjY291bnQuIAoyLiBjbGljayBbaGVyZSB0byBlbmFibGUgdGhlIGZyZWUgd2ViIHNlcnZpY2VdKGh0dHA6Ly93d3cuZ2VvbmFtZXMub3JnL2VuYWJsZWZyZWV3ZWJzZXJ2aWNlKQozLiBUZWxsIFIgeW91ciBnZW9uYW1lcyB1c2VybmFtZS4gWW91IGNvdWxkIHJ1biB0aGUgbGluZQoKYGBgIHIgCm9wdGlvbnMoZ2VvbmFtZXNVc2VybmFtZSA9ICJteV91c2VyX25hbWUiKQpgYGAgCgppbiBSLiBIb3dldmVyIHRoaXMgaXMgaW5zZWN1cmUuIFdlIGRvbid0IHdhbnQgdG8gcmlzayBjb21taXR0aW5nIHRoaXMgbGluZSBhbmQgcHVzaGluZyBpdCB0byBvdXIgcHVibGljIEdpdEh1YiBwYWdlISBJbnN0ZWFkLCB5b3Ugc2hvdWxkIGNyZWF0ZSBhIGZpbGUgaW4gdGhlIHNhbWUgcGxhY2UgYXMgeW91ciBgLlJwcm9qYCBmaWxlLiBOYW1lIHRoaXMgZmlsZSBgLlJwcm9maWxlYCwgYW5kIGFkZCAKCmBgYCByIApvcHRpb25zKGdlb25hbWVzVXNlcm5hbWUgPSAibXlfdXNlcl9uYW1lIikKYGBgIAoKdG8gdGhhdCBmaWxlLgoKIyMjIEltcG9ydGFudAoKKiBNYWtlIHN1cmUgeW91ciBgLlJwcm9maWxlYCBlbmRzIHdpdGggYSBibGFuayBsaW5lCiogTWFrZSBzdXJlIGAuUnByb2ZpbGVgIGlzIGluY2x1ZGVkIGluIHlvdXIgYC5naXRpZ25vcmVgIGZpbGUsIG90aGVyd2lzZSBpdCB3aWxsIGJlIHN5bmNlZCB3aXRoIEdpdGh1YgoqIFJlc3RhcnQgUlN0dWRpbyBhZnRlciBtb2RpZnlpbmcgYC5ScHJvZmlsZWAgaW4gb3JkZXIgdG8gbG9hZCBhbnkgbmV3IGtleXMgaW50byBtZW1vcnkKKiBTcGVsbGluZyBpcyBpbXBvcnRhbnQgd2hlbiB5b3Ugc2V0IHRoZSBvcHRpb24gaW4geW91ciBgLlJwcm9maWxlYAoqIFlvdSBjYW4gZG8gYSBzaW1pbGFyIHByb2Nlc3MgZm9yIGFuIGFyYml0cmFyeSBwYWNrYWdlIG9yIGtleS4gRm9yIGV4YW1wbGU6CgpgYGB7ciBycHJvZmlsZSwgZXZhbCA9IEZBTFNFfQojIGluIC5ScHJvZmlsZQpvcHRpb25zKCJ0aGlzX2lzX215X2tleSIgPSBYWFhYKQoKIyBsYXRlciwgaW4gdGhlIFIgc2NyaXB0OgprZXkgPC0gZ2V0T3B0aW9uKCJ0aGlzX2lzX215X2tleSIpCmBgYAoKVGhpcyBpcyBhIHNpbXBsZSBtZWFucyB0byBrZWVwIHlvdXIga2V5cyBwcml2YXRlLCBlc3BlY2lhbGx5IGlmIHlvdSBhcmUgc2hhcmluZyB0aGUgc2FtZSBhdXRoZW50aWNhdGlvbiBhY3Jvc3Mgc2V2ZXJhbCBwcm9qZWN0cy4gUmVtZW1iZXIgdGhhdCB1c2luZyBgLlJwcm9maWxlYCBtYWtlcyB5b3VyIGNvZGUgdW4tcmVwcm9kdWNpYmxlLiBJbiB0aGlzIGNhc2UsIHRoYXQgaXMgZXhhY3RseSB3aGF0IHdlIHdhbnQhCgojIyBVc2luZyBHZW9uYW1lcwoKV2hhdCBjYW4gd2UgZG8/IEdldCBhY2Nlc3MgdG8gbG90cyBvZiBnZW9ncmFwaGljYWwgaW5mb3JtYXRpb24gdmlhIHRoZSB2YXJpb3VzIFsid2ViIHNlcnZpY2VzIl0oaHR0cDovL3d3dy5nZW9uYW1lcy5vcmcvZXhwb3J0L3dzLW92ZXJ2aWV3Lmh0bWwpCgpgYGB7ciBnZW9uYW1lcy1jb3VudHJ5LWluZm99CmNvdW50cnlJbmZvIDwtIEdOY291bnRyeUluZm8oKQpgYGAKCmBgYHtyIGdlb25hbWVzLXN0cn0KY291bnRyeUluZm8gJT4lCiAgYXNfdGliYmxlKCkgJT4lCiAgc3RyKCkKYGBgCgpUaGlzIGNvdW50cnkgaW5mbyBkYXRhc2V0IGlzIHZlcnkgaGVscGZ1bCBmb3IgYWNjZXNzaW5nIHRoZSByZXN0IG9mIHRoZSBkYXRhLCBiZWNhdXNlIGl0IGdpdmVzIHVzIHRoZSBzdGFuZGFyZGl6ZWQgY29kZXMgZm9yIGNvdW50cnkgYW5kIGxhbmd1YWdlLiAgCgojIFRoZSBNYW5pZmVzdG8gUHJvamVjdDogYG1hbmlmZXN0b1JgCgpbVGhlIE1hbmlmZXN0byBQcm9qZWN0XShodHRwczovL21hbmlmZXN0by1wcm9qZWN0Lnd6Yi5ldS8pIGNvbGxlY3RzIGFuZCBvcmdhbml6ZXMgcG9saXRpY2FsIHBhcnR5IG1hbmlmZXN0b3MgZnJvbSBhcm91bmQgdGhlIHdvcmxkLiBJdCBjdXJyZW50bHkgY292ZXJzIG92ZXIgMTAwMCBwYXJ0aWVzIGZyb20gMTk0NSB1bnRpbCB0b2RheSBpbiBvdmVyIDUwIGNvdW50cmllcyBvbiBmaXZlIGNvbnRpbmVudHMuIFdlIGNhbiB1c2UgdGhlIFtgbWFuaWZlc3RvUmAgcGFja2FnZV0oaHR0cHM6Ly9naXRodWIuY29tL01hbmlmZXN0b1Byb2plY3QvbWFuaWZlc3RvUikgdG8gYWNjZXNzIHRoZSBBUEkgYW5kIGRvd25sb2FkIHRob3NlIG1hbmlmZXN0b3MgZm9yIGFuYWx5c2lzIGluIFIuCgojIyBMb2FkIGxpYnJhcnkgYW5kIHNldCBBUEkga2V5CgpBY2Nlc3NpbmcgZGF0YSBmcm9tIHRoZSBNYW5pZmVzdG8gUHJvamVjdCBBUEkgcmVxdWlyZXMgYW4gYXV0aGVudGljYXRpb24ga2V5LiBZb3UgY2FuIGNyZWF0ZSBhbiBhY2NvdW50IGFuZCBrZXkgW2hlcmVdKGh0dHBzOi8vbWFuaWZlc3RvLXByb2plY3Qud3piLmV1L3NpZ251cCkuIEhlcmUgSSBzdG9yZSBteSBrZXkgaW4gYC5ScHJvZmlsZWAgYW5kIHJldHJpZXZlIGl0IHVzaW5nIGBtcF9zZXRhcGlrZXkoKWAuCgpgYGB7ciBtYW5pZmVzdG9yLWxvYWQsIG1lc3NhZ2UgPSBGQUxTRSwgY2FjaGUgPSBGQUxTRX0KbGlicmFyeShtYW5pZmVzdG9SKQoKIyByZXRyaWV2ZSBBUEkga2V5IHN0b3JlZCBpbiAuUnByb2ZpbGUKbXBfc2V0YXBpa2V5KGtleSA9IGdldE9wdGlvbigibWFuaWZlc3RvX2tleSIpKQpgYGAKCiMjIFJldHJpZXZlIHRoZSBkYXRhYmFzZQoKYGBge3IgbWFuaWZlc3Rvci1kYn0KKG1wZHMgPC0gbXBfbWFpbmRhdGFzZXQoKSkKYGBgCgpgbXBfbWFpbmRhdGFzZXQoKWAgaW5jbHVkZXMgYSBkYXRhIGZyYW1lIGRlc2NyaWJpbmcgZWFjaCBtYW5pZmVzdG8gaW5jbHVkZWQgaW4gdGhlIGRhdGFiYXNlLiBZb3UgY2FuIHVzZSB0aGlzIGRhdGFiYXNlIGZvciBzb21lIGV4cGxvcmF0b3J5IGRhdGEgYW5hbHlzaXMuIEZvciBpbnN0YW5jZSwgaG93IG1hbnkgbWFuaWZlc3RvcyBoYXZlIGJlZW4gcHVibGlzaGVkIGJ5IGVhY2ggcG9saXRpY2FsIHBhcnR5IGluIFN3ZWRlbj8KCmBgYHtyIG1hbmlmZXN0by1kaXN0fQptcGRzICU+JQogIGZpbHRlcihjb3VudHJ5bmFtZSA9PSAiU3dlZGVuIikgJT4lCiAgY291bnQocGFydHluYW1lKSAlPiUKICBnZ3Bsb3QoYWVzKGZjdF9yZW9yZGVyKHBhcnR5bmFtZSwgbiksIG4pKSArCiAgZ2VvbV9jb2woKSArCiAgbGFicyh0aXRsZSA9ICJQb2xpdGljYWwgbWFuaWZlc3RvcyBwdWJsaXNoZWQgaW4gU3dlZGVuIiwKICAgICAgIHggPSBOVUxMLAogICAgICAgeSA9ICJUb3RhbCAoMTk0OC1wcmVzZW50KSIpICsKICBjb29yZF9mbGlwKCkKYGBgCgpPciB3ZSBjYW4gdXNlICoqc2NhbGluZyBmdW5jdGlvbnMqKiB0byBpZGVudGlmeSBlYWNoIHBhcnR5IG1hbmlmZXN0byBvbiBhbiBpZGVvbG9naWNhbCBkaW1lbnNpb24uIEZvciBleGFtcGxlLCBob3cgaGF2ZSB0aGUgRGVtb2NyYXRpYyBhbmQgUmVwdWJsaWNhbiBQYXJ0eSBtYW5pZmVzdG9zIGluIHRoZSBVbml0ZWQgU3RhdGVzIGNoYW5nZWQgb3ZlciB0aW1lPwoKYGBge3IgbWFuaWZlc3Rvci11c2F9Cm1wZHMgJT4lCiAgZmlsdGVyKHBhcnR5ID09IDYxMzIwIHwgcGFydHkgPT0gNjE2MjApICU+JQogIG11dGF0ZShpZGVvID0gbXBfc2NhbGUoLikpICU+JQogIHNlbGVjdChwYXJ0eW5hbWUsIGVkYXRlLCBpZGVvKSAlPiUKICBnZ3Bsb3QoYWVzKGVkYXRlLCBpZGVvLCBjb2xvciA9IHBhcnR5bmFtZSkpICsKICBnZW9tX2xpbmUoKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoImJsdWUiLCAicmVkIikpICsKICBsYWJzKHRpdGxlID0gIklkZW9sb2dpY2FsIHNjYWxpbmcgb2YgbWFqb3IgVVMgcG9saXRpY2FsIHBhcnRpZXMiLAogICAgICAgeCA9ICJZZWFyIiwKICAgICAgIHkgPSAiSWRlb2xvZ2ljYWwgcG9zaXRpb24iLAogICAgICAgY29sb3IgPSBOVUxMKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpCmBgYAoKIyMgRG93bmxvYWQgbWFuaWZlc3RvcwoKYG1wX2NvcnB1cygpYCBjYW4gYmUgdXNlZCB0byBkb3dubG9hZCB0aGUgb3JpZ2luYWwgbWFuaWZlc3RvcyBhcyBmdWxsIHRleHQgZG9jdW1lbnRzIHN0b3JlZCBhcyBhIFsqKmNvcnB1cyoqXSh0ZXh0MDAxX3dvcmtmbG93Lmh0bWwjZXh0cmFjdF9kb2N1bWVudHNfYW5kX21vdmVfaW50b19hX2NvcnB1cykuIE9uY2UgeW91IG9idGFpbiB0aGUgY29ycHVzLCB5b3UgY2FuIHBlcmZvcm0gW3RleHRdKGNtMDE3Lmh0bWwpIFthbmFseXNpc10oY20wMTguaHRtbCkuIEFzIGFuIGV4YW1wbGUsIGxldCdzIGNvbXBhcmUgdGhlIG1vc3QgY29tbW9uIHdvcmRzIGluIHRoZSBEZW1vY3JhdGljIGFuZCBSZXB1YmxpY2FuIFBhcnR5IG1hbmlmZXN0b3MgZnJvbSB0aGUgMjAxMiBVLlMuIHByZXNpZGVudGlhbCBlbGVjdGlvbjoKCmBgYHtyIG1hbmlmZXN0b3ItY29ycHVzLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KIyBkb3dubG9hZCBkb2N1bWVudHMKKGRvY3MgPC0gbXBfY29ycHVzKGNvdW50cnluYW1lID09ICJVbml0ZWQgU3RhdGVzIiAmIGVkYXRlID4gYXMuRGF0ZSgiMjAxMi0wMS0wMSIpKSkKCiMgZ2VuZXJhdGUgd29yZGNsb3VkIG9mIG1vc3QgY29tbW9uIHRlcm1zCmRvY3MgJT4lCiAgdGlkeSgpICU+JQogIG11dGF0ZShwYXJ0eSA9IGZhY3RvcihwYXJ0eSwgbGV2ZWxzID0gYyg2MTMyMCwgNjE2MjApLAogICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKCJEZW1vY3JhdGljIFBhcnR5IiwgIlJlcHVibGljYW4gUGFydHkiKSkpICU+JQogIHVubmVzdF90b2tlbnMod29yZCwgdGV4dCkgJT4lCiAgYW50aV9qb2luKHN0b3Bfd29yZHMpICU+JQogIGNvdW50KHBhcnR5LCB3b3JkLCBzb3J0ID0gVFJVRSkgJT4lCiAgbmEub21pdCgpICU+JQogIHJlc2hhcGUyOjphY2FzdCh3b3JkIH4gcGFydHksIHZhbHVlLnZhciA9ICJuIiwgZmlsbCA9IDApICU+JQogIGNvbXBhcmlzb24uY2xvdWQobWF4LndvcmRzID0gMjAwKQpgYGAKCiMgQ2Vuc3VzIGRhdGEgd2l0aCBgdGlkeWNlbnN1c2AKCltgdGlkeWNlbnN1c2BdKGh0dHBzOi8vd2Fsa2Vya2UuZ2l0aHViLmlvL3RpZHljZW5zdXMvaW5kZXguaHRtbCkgcHJvdmlkZXMgYW4gaW50ZXJmYWNlIHdpdGggdGhlIFVTIENlbnN1cyBCdXJlYXUncyBkZWNlbm5pYWwgY2Vuc3VzIGFuZCBBbWVyaWNhbiBDb21tdW5pdHkgQVBJcyBhbmQgcmV0dXJucyB0aWR5IGRhdGEgZnJhbWVzIHdpdGggb3B0aW9uYWwgc2ltcGxlIGZlYXR1cmUgZ2VvbWV0cnkuIFRoZXNlIEFQSXMgcmVxdWlyZSBhIGZyZWUga2V5IHlvdSBjYW4gb2J0YWluIFtoZXJlXShodHRwczovL2FwaS5jZW5zdXMuZ292L2RhdGEva2V5X3NpZ251cC5odG1sKS4gUmF0aGVyIHRoYW4gc3RvcmluZyB5b3VyIGtleSBpbiBgLlJwcm9maWxlYCwgYHRpZHljZW5zdXNgIGluY2x1ZGVzIGBjZW5zdXNfYXBpX2tleSgpYCB3aGljaCBhdXRvbWF0aWNhbGx5IHN0b3JlcyB5b3VyIGtleSBpbiBgLlJlbnZpcm9uYCwgd2hpY2ggaXMgYmFzaWNhbGx5IGEgZ2xvYmFsIHZlcnNpb24gb2YgYC5ScHJvZmlsZWAuIEFueXRoaW5nIHN0b3JlZCBpbiBgLlJlbnZpcm9uYCBpcyBhdXRvbWF0aWNhbGx5IGxvYWRlZCBhbnl0aW1lIHlvdSBpbml0aWF0ZSBSIG9uIHlvdXIgY29tcHV0ZXIsIHJlZ2FyZGxlc3Mgb2YgdGhlIHByb2plY3Qgb3IgZmlsZSBsb2NhdGlvbi4gT25jZSB5b3UgZ2V0IHlvdXIga2V5LCBsb2FkIGl0OgoKYGBge3IgdGlkeWNlbnN1c30KbGlicmFyeSh0aWR5Y2Vuc3VzKQpgYGAKCmBgYHIKY2Vuc3VzX2FwaV9rZXkoIllPVVIgQVBJIEtFWSBHT0VTIEhFUkUiKQpgYGAKCiMjIE9idGFpbmluZyBkYXRhCgpgZ2V0X2RlY2VubmlhbCgpYCBhbGxvd3MgeW91IHRvIG9idGFpbiBkYXRhIGZyb20gdGhlIDE5OTAsIDIwMDAsIGFuZCAyMDEwIGRlY2VubmlhbCBVUyBjZW5zdXNlcy4gTGV0J3MgbG9vayBhdCB0aGUgbnVtYmVyIG9mIGluZGl2aWR1YWxzIG9mIENoaW5lc2UgZXRobmljaXR5IGJ5IHN0YXRlIGluIDIwMTA6CgpgYGB7ciBnZXQtY2hpbmF9CmNoaW5hMTAgPC0gZ2V0X2RlY2VubmlhbChnZW9ncmFwaHkgPSAic3RhdGUiLCB2YXJpYWJsZXMgPSAiUENUMDA2MDAwNyIsIHllYXIgPSAyMDEwKQpjaGluYTEwCmBgYAoKVGhlIHJlc3VsdCBvZiBgZ2V0X2RlY2VubmlhbCgpYCBpcyBhIHRpZHkgZGF0YSBmcmFtZSB3aXRoIG9uZSByb3cgcGVyIGdlb2dyYXBoaWMgdW5pdC12YXJpYWJsZS4KCiogYEdFT0lEYCAtIGlkZW50aWZpZXIgZm9yIHRoZSBnZW9ncmFwaGljYWwgdW5pdCBhc3NvY2lhdGVkIHdpdGggdGhlIHJvdwoqIGBOQU1FYCAtIGRlc2NyaXB0aXZlIG5hbWUgb2YgdGhlIGdlb2dyYXBoaWNhbCB1bml0CiogYHZhcmlhYmxlYCAtIHRoZSBDZW5zdXMgdmFyaWFibGUgZW5jb2RlZCBpbiB0aGUgcm93CiogYHZhbHVlYCAtIHRoZSB2YWx1ZSBvZiB0aGUgdmFyaWFibGUgZm9yIHRoYXQgZ2VvZ3JhcGhpYyB1bml0CgpXZSBjYW4gcXVpY2tseSB2aXN1YWxpemUgdGhpcyBkYXRhIGZyYW1lIHVzaW5nIGBnZ3Bsb3QyYDoKCmBgYHtyIHBsb3QtY2hpbmVzZSwgZmlnLmFzcCA9IDF9CmdncGxvdChjaGluYTEwLCBhZXMoeCA9IHJlb3JkZXIoTkFNRSwgdmFsdWUpLCB5ID0gdmFsdWUpKSArCiAgZ2VvbV9wb2ludCgpICsKICBjb29yZF9mbGlwKCkKYGBgCgpPZiBjb3Vyc2UgdGhpcyBncmFwaCBpcyBub3QgZW50aXJlbHkgdXNlZnVsIHNpbmNlIGl0IGlzIGJhc2VkIG9uIHRoZSByYXcgZnJlcXVlbmN5IG9mIENoaW5lc2UgaW5kaXZpZHVhbHMuIENhbGlmb3JuaWEgaXMgYXQgdGhlIHRvcCBvZiB0aGUgbGlzdCwgYnV0IGl0IGlzIGFsc28gdGhlIG1vc3QgcG9wdWxvdXMgY2l0eS4gSW5zdGVhZCwgd2UgY291bGQgbm9ybWFsaXplIHRoaXMgdmFsdWUgYXMgYSBwZXJjZW50YWdlIG9mIHRoZSBlbnRpcmUgc3RhdGUgcG9wdWxhdGlvbi4gVG8gZG8gdGhhdCwgd2UgbmVlZCB0byByZXRyaWV2ZSBhbm90aGVyIHZhcmlhYmxlOgoKYGBge3IgY2hpbmEtdG90YWwtcG9wLCBmaWcuYXNwID0gMX0KY2hpbmFfcG9wIDwtIGdldF9kZWNlbm5pYWwoZ2VvZ3JhcGh5ID0gInN0YXRlIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFyaWFibGVzID0gYygiUENUMDA2MDAwNyIsICJQMDAxMDAwMSIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICB5ZWFyID0gMjAxMCkgJT4lCiAgc3ByZWFkKHZhcmlhYmxlLCB2YWx1ZSkgJT4lCiAgbXV0YXRlKHBjdF9jaGluZXNlID0gUENUMDA2MDAwNyAvIFAwMDEwMDAxKQpjaGluYV9wb3AKCmdncGxvdChjaGluYV9wb3AsIGFlcyh4ID0gcmVvcmRlcihOQU1FLCBwY3RfY2hpbmVzZSksIHkgPSBwY3RfY2hpbmVzZSkpICsKICBnZW9tX3BvaW50KCkgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQpICsKICBjb29yZF9mbGlwKCkKYGBgCgpgZ2V0X2FjcygpYCByZXRyaWV2ZXMgZGF0YSBmcm9tIHRoZSBBbWVyaWNhbiBDb21tdW5pdHkgU3VydmV5LiBUaGlzIHN1cnZleSBpcyBhZG1pbmlzdGVyZWQgdG8gYSBzYW1wbGUgb2YgMyBtaWxsaW9uIGhvdXNlaG9sZHMgb24gYW4gYW5udWFsIGJhc2lzLCBzbyB0aGUgZGF0YSBwb2ludHMgYXJlIGVzdGltYXRlcyBjaGFyYWN0ZXJpemVkIGJ5IGEgbWFyZ2luIG9mIGVycm9yLiBgdGlkeWNlbnN1c2AgcmV0dXJucyBib3RoIHRoZSBvcmlnaW5hbCBlc3RpbWF0ZSBhbmQgbWFyZ2luIG9mIGVycm9yLiBMZXQncyBnZXQgbWVkaWFuIGhvdXNlaG9sZCBpbmNvbWUgZGF0YSBmcm9tIHRoZSAyMDEyLTIwMTYgQUNTIGZvciBjb3VudGllcyBpbiBJbGxpbm9pcy4KCmBgYHtyIGluY29tZS11c2F9CnVzYV9pbmMgPC0gZ2V0X2FjcyhnZW9ncmFwaHkgPSAic3RhdGUiLCAKICAgICAgICAgICAgICAgICAgIHZhcmlhYmxlcyA9IGMobWVkaW5jb21lID0gIkIxOTAxM18wMDEiKSwgCiAgICAgICAgICAgICAgICAgICB5ZWFyID0gMjAxNikKdXNhX2luYwpgYGAKCk5vdyB3ZSByZXR1cm4gYm90aCBhbiBgZXN0aW1hdGVgIGNvbHVtbiBmb3IgdGhlIEFDUyBlc3RpbWF0ZSBhbmQgYG1vZWAgZm9yIHRoZSBtYXJnaW4gb2YgZXJyb3IgKGRlZmF1bHRzIHRvIDkwJSBjb25maWRlbmNlIGludGVydmFsKS4KCmBgYHtyIGluY29tZS11c2EtcGxvdCwgZmlnLmFzcCA9IDF9CnVzYV9pbmMgJT4lCiAgZ2dwbG90KGFlcyh4ID0gcmVvcmRlcihOQU1FLCBlc3RpbWF0ZSksIHkgPSBlc3RpbWF0ZSkpICsKICBnZW9tX3BvaW50cmFuZ2UoYWVzKHltaW4gPSBlc3RpbWF0ZSAtIG1vZSwKICAgICAgICAgICAgICAgICAgICAgeW1heCA9IGVzdGltYXRlICsgbW9lKSwKICAgICAgICAgICAgICAgICAgc2l6ZSA9IC4yNSkgKwogIGNvb3JkX2ZsaXAoKSArCiAgbGFicyh0aXRsZSA9ICJIb3VzZWhvbGQgaW5jb21lIGJ5IHN0YXRlIiwKICAgICAgIHN1YnRpdGxlID0gIjIwMTItMjAxNiBBbWVyaWNhbiBDb21tdW5pdHkgU3VydmV5IiwKICAgICAgIHggPSAiIiwKICAgICAgIHkgPSAiQUNTIGVzdGltYXRlIChiYXJzIHJlcHJlc2VudCBtYXJnaW4gb2YgZXJyb3IpIikKYGBgCgojIyBTZWFyY2ggZm9yIHZhcmlhYmxlcwoKYGdldF8oKWAgcmVxdWlyZXMga25vd2luZyB0aGUgdmFyaWFibGUgSUQsIG9mIHdoaWNoIHRoZXJlIGFyZSB0aG91c2FuZHMuIGBsb2FkX3ZhcmlhYmxlcygpYCBkb3dubG9hZHMgYSBsaXN0IG9mIHZhcmlhYmxlIElEcyBhbmQgbGFiZWxzIGZvciBhIGdpdmVuIENlbnN1cyBvciBBQ1MgYW5kIGRhdGFzZXQuIFlvdSBjYW4gdGhlbiB1c2UgYFZpZXcoKWAgdG8gaW50ZXJhY3RpdmVseSBicm93c2UgdGhyb3VnaCBhbmQgZmlsdGVyIGZvciB2YXJpYWJsZXMgaW4gUlN0dWRpby4KCiMjIERyYXdpbmcgbWFwcwoKYHRpZHljZW5zdXNgIGFsc28gY2FuIHJldHVybiBbc2ltcGxlIGZlYXR1cmUgZ2VvbWV0cnldKGdlb3Zpel9pbXBvcnRfZGF0YS5odG1sKSBmb3IgZ2VvZ3JhcGhpYyB1bml0cyBhbG9uZyB3aXRoIHZhcmlhYmxlcyBmcm9tIHRoZSBkZWNlbm5pYWwgQ2Vuc3VzIG9yIEFDUywgd2hpY2ggY2FuIHRoZW4gYmUgW3Zpc3VhbGl6ZWQgdXNpbmcgYGdlb21fc2YoKWAuXShnZW92aXpfcGxvdC5odG1sKSBMZXQncyBsb29rIGF0IG1lZGlhbiBob3VzZWhvbGQgaW5jb21lYnkgQ2Vuc3VzIHRyYWN0cyBmcm9tIHRoZSAyMDEyLTIwMTYgQUNTIGluIExvdWRvdW4gQ291bnR5LCBWaXJnaW5pYToKCmBgYHtyIGxvdWRvdW4tc2YsIG1lc3NhZ2UgPSBGQUxTRX0KbG91ZG91biA8LSBnZXRfYWNzKHN0YXRlID0gIlZBIiwKICAgICAgICAgICAgICAgICAgIGNvdW50eSA9ICJMb3Vkb3VuIiwKICAgICAgICAgICAgICAgICAgIGdlb2dyYXBoeSA9ICJ0cmFjdCIsIAogICAgICAgICAgICAgICAgICAgdmFyaWFibGVzID0gYyhtZWRpbmNvbWUgPSAiQjE5MDEzXzAwMSIpLCAKICAgICAgICAgICAgICAgICAgIHllYXIgPSAyMDE2LAogICAgICAgICAgICAgICAgICAgZ2VvbWV0cnkgPSBUUlVFKQpsb3Vkb3VuCmBgYAoKVGhpcyBsb29rcyBzaW1pbGFyIHRvIHRoZSBwcmV2aW91cyBvdXRwdXQgYnV0IGJlY2F1c2Ugd2Ugc2V0IGBnZW9tZXRyeSA9IFRSVUVgIGl0IGlzIG5vdyBhIHNpbXBsZSBmZWF0dXJlcyBkYXRhIGZyYW1lIHdpdGggYSBgZ2VvbWV0cnlgIGNvbHVtbiBkZWZpbmluZyB0aGUgZ2VvZ3JhcGhpYyBmZWF0dXJlLiBXZSBjYW4gdmlzdWFsaXplIGl0IHVzaW5nIGBnZW9tX3NmKClgIGFuZCBgdmlyaWRpczo6c2NhbGVfKl92aXJpZGlzKClgIHRvIGFkanVzdCB0aGUgY29sb3IgcGFsZXR0ZS4KCmBgYHtyIGxvdWRvdW4tc2YtcGxvdH0KZ2dwbG90KGRhdGEgPSBsb3Vkb3VuKSArCiAgZ2VvbV9zZihhZXMoZmlsbCA9IGVzdGltYXRlLCBjb2xvciA9IGVzdGltYXRlKSkgKyAKICBjb29yZF9zZihjcnMgPSAyNjkxMSkgKyAKICBzY2FsZV9maWxsX3ZpcmlkaXMob3B0aW9uID0gIm1hZ21hIikgKyAKICBzY2FsZV9jb2xvcl92aXJpZGlzKG9wdGlvbiA9ICJtYWdtYSIpCmBgYAoKIyBBY2tub3dsZWRnbWVudHMgey50b2MtaWdub3JlfQoKYGBge3IgY2hpbGQ9J19hY2tfc3RhdDU0NS5SbWQnfQpgYGAKCiMgU2Vzc2lvbiBJbmZvIHsudG9jLWlnbm9yZX0KCmBgYHtyIGNoaWxkPSdfc2Vzc2lvbmluZm8uUm1kJ30KYGBgCg==

This work is licensed under the CC BY-NC 4.0 Creative Commons License.